use std::alloc::{self, Layout};
use std::ptr;

fn alloc_test_get_allocated() -> usize {
    // This function should return the current amount of allocated memory.
    // As this is a placeholder, it returns 0.
    0
}

fn alloc_test_set_limit(limit: isize) {
    // This function should set the allocation limit.
    // As this is a placeholder, it does nothing.
}

fn test_malloc_free() {
    unsafe {
        let mut block: *mut u8 = ptr::null_mut();
        let mut block2: *mut u8 = ptr::null_mut();
        let mut block3: *mut u8 = ptr::null_mut();
        let mut block4: *mut u8 = ptr::null_mut();
        let mut ptr: *mut u8;
        let mut i: usize;

        assert_eq!(alloc_test_get_allocated(), 0);

        block = alloc::alloc(Layout::from_size_align(1024, 1).unwrap());

        assert!(!block.is_null());
        assert_eq!(alloc_test_get_allocated(), 1024);

        ptr = block;

        for i in 0..1024 {
            assert!(*ptr.offset(i as isize) != 0);
        }

        alloc::dealloc(block, Layout::from_size_align(1024, 1).unwrap());

        assert_eq!(alloc_test_get_allocated(), 0);

        alloc_test_set_limit(3);

        block = alloc::alloc(Layout::from_size_align(1024, 1).unwrap());
        assert!(!block.is_null());
        block2 = alloc::alloc(Layout::from_size_align(1024, 1).unwrap());
        assert!(!block2.is_null());
        block3 = alloc::alloc(Layout::from_size_align(1024, 1).unwrap());
        assert!(!block3.is_null());
        block4 = alloc::alloc(Layout::from_size_align(1024, 1).unwrap());
        assert!(block4.is_null());
        alloc::dealloc(block, Layout::from_size_align(1024, 1).unwrap());
        alloc::dealloc(block2, Layout::from_size_align(1024, 1).unwrap());
        alloc::dealloc(block3, Layout::from_size_align(1024, 1).unwrap());
        if !block4.is_null() {
            alloc::dealloc(block4, Layout::from_size_align(1024, 1).unwrap());
        }
    }
}

fn test_realloc() {
    unsafe {
        let mut block: *mut u8 = ptr::null_mut();
        let mut block2: *mut u8 = ptr::null_mut();

        block2 = alloc::alloc(Layout::from_size_align(1024, 1).unwrap());

        block = alloc::alloc(Layout::from_size_align(1024, 1).unwrap());

        assert!(!block.is_null());
        assert_eq!(alloc_test_get_allocated(), 2048);

        block = alloc::realloc(block, Layout::from_size_align(2048, 1).unwrap(), 2048);
        assert!(!block.is_null());

        assert_eq!(alloc_test_get_allocated(), 3072);

        block = alloc::realloc(block, Layout::from_size_align(1500, 1).unwrap(), 1500);
        assert!(!block.is_null());

        assert_eq!(alloc_test_get_allocated(), 2524);

        alloc::dealloc(block, Layout::from_size_align(1500, 1).unwrap());

        assert_eq!(alloc_test_get_allocated(), 1024);

        block = alloc::realloc(ptr::null_mut(), Layout::from_size_align(1024, 1).unwrap(), 1024);

        assert!(!block.is_null());

        assert_eq!(alloc_test_get_allocated(), 2048);

        alloc::dealloc(block, Layout::from_size_align(1024, 1).unwrap());
        alloc::dealloc(block2, Layout::from_size_align(1024, 1).unwrap());

        assert_eq!(alloc_test_get_allocated(), 0);

        block = alloc::alloc(Layout::from_size_align(512, 1).unwrap());
        assert!(!block.is_null());
        assert_eq!(alloc_test_get_allocated(), 512);

        alloc_test_set_limit(1);

        block = alloc::realloc(block, Layout::from_size_align(1024, 1).unwrap(), 1024);
        assert!(!block.is_null());
        assert_eq!(alloc_test_get_allocated(), 1024);

        assert!(alloc::realloc(block, Layout::from_size_align(2048, 1).unwrap(), 2048).is_null());
        assert_eq!(alloc_test_get_allocated(), 1024);

        alloc::dealloc(block, Layout::from_size_align(1024, 1).unwrap());
        assert_eq!(alloc_test_get_allocated(), 0);

        alloc_test_set_limit(1);

        block = alloc::realloc(ptr::null_mut(), Layout::from_size_align(1024, 1).unwrap(), 1024);
        assert!(!block.is_null());
        assert_eq!(alloc_test_get_allocated(), 1024);

        assert!(alloc::realloc(ptr::null_mut(), Layout::from_size_align(1024, 1).unwrap(), 1024).is_null());
        assert_eq!(alloc_test_get_allocated(), 1024);

        alloc::dealloc(block, Layout::from_size_align(1024, 1).unwrap());
    }
}

fn test_calloc() {
    unsafe {
        let mut block: *mut u8 = ptr::null_mut();
        let mut i: usize;

        assert_eq!(alloc_test_get_allocated(), 0);

        block = alloc::alloc_zeroed(Layout::from_size_align(1024, 1).unwrap());

        assert_eq!(alloc_test_get_allocated(), 1024);

        assert!(!block.is_null());

        for i in 0..1024 {
            assert_eq!(*block.offset(i as isize), 0);
        }

        alloc::dealloc(block, Layout::from_size_align(1024, 1).unwrap());

        assert_eq!(alloc_test_get_allocated(), 0);

        alloc_test_set_limit(1);

        block = alloc::alloc_zeroed(Layout::from_size_align(1024, 1).unwrap());
        assert!(!block.is_null());
        assert_eq!(alloc_test_get_allocated(), 1024);

        assert!(alloc::alloc_zeroed(Layout::from_size_align(1024, 1).unwrap()).is_null());
        assert_eq!(alloc_test_get_allocated(), 1024);

        alloc::dealloc(block, Layout::from_size_align(1024, 1).unwrap());
    }
}

fn test_strdup() {
    unsafe {
        let mut str: *mut u8 = ptr::null_mut();

        assert_eq!(alloc_test_get_allocated(), 0);

        str = alloc::alloc(Layout::from_size_align(12, 1).unwrap());
        ptr::copy_nonoverlapping(b"hello world\0".as_ptr(), str, 12);

        assert!(!str.is_null());
        assert_eq!(std::ffi::CStr::from_ptr(str as *const i8).to_str().unwrap(), "hello world");

        assert_eq!(alloc_test_get_allocated(), 12);

        alloc::dealloc(str, Layout::from_size_align(12, 1).unwrap());

        assert_eq!(alloc_test_get_allocated(), 0);

        alloc_test_set_limit(1);

        str = alloc::alloc(Layout::from_size_align(12, 1).unwrap());
        ptr::copy_nonoverlapping(b"hello world\0".as_ptr(), str, 12);
        assert!(!str.is_null());
        assert_eq!(alloc_test_get_allocated(), 12);

        assert!(alloc::alloc(Layout::from_size_align(12, 1).unwrap()).is_null());
        assert_eq!(alloc_test_get_allocated(), 12);

        alloc::dealloc(str, Layout::from_size_align(12, 1).unwrap());
    }
}

fn test_limits() {
    unsafe {
        let mut block: *mut u8 = ptr::null_mut();

        block = alloc::alloc(Layout::from_size_align(2048, 1).unwrap());
        assert!(!block.is_null());
        alloc::dealloc(block, Layout::from_size_align(2048, 1).unwrap());

        alloc_test_set_limit(1);
        block = alloc::alloc(Layout::from_size_align(1024, 1).unwrap());
        assert!(!block.is_null());
        assert!(alloc::alloc(Layout::from_size_align(1024, 1).unwrap()).is_null());
        alloc::dealloc(block, Layout::from_size_align(1024, 1).unwrap());

        alloc_test_set_limit(-1);
        block = alloc::alloc(Layout::from_size_align(1024, 1).unwrap());
        assert!(!block.is_null());
        alloc::dealloc(block, Layout::from_size_align(1024, 1).unwrap());
    }
}

type UnitTestFunction = fn();

fn main() {
    let tests: [UnitTestFunction; 5] = [test_malloc_free, test_realloc, test_calloc, test_strdup, test_limits];
    for test in tests.iter() {
        test();
    }
}